﻿namespace ImageWallControl
{
    using System;
    using System.Collections.Generic;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Media.Effects;
    using System.Windows.Shapes;
    using System.Windows.Threading;
    using BrushProviders;

    public class ImageWall : Panel
    {
        private const int Cols = 12;
        public const int FrameSize = 70;
        private const int MarginSize = 2;
        private const int Rows = 10;
        public const int SectionHeight = Rows*FrameSize;
        public const int SectionWidth = Cols*FrameSize;

        public static DependencyProperty BrushProviderProperty = DependencyProperty.Register(
            "BrushProvider",
            typeof (IBrushProvider),
            typeof (ImageWall),
            new PropertyMetadata(new RandomColorBrushProvider()));

        private readonly DispatcherTimer _timer = new DispatcherTimer();
        private bool[,] _positions;

        public ImageWall()
        {
            _timer.Interval = TimeSpan.FromSeconds(8.0);
            _timer.Tick += OnTick;
            Loaded += delegate { Refresh(); };
        }

        public IBrushProvider BrushProvider
        {
            get { return (IBrushProvider) GetValue(BrushProviderProperty); }
            set { SetValue(BrushProviderProperty, value); }
        }

        private void OnTick(object sender, EventArgs e)
        {
            var index = Random.Generator.Next(0, Children.Count - 1);
            var child = Children[index];
            var brush = BrushProvider.GetBrush();

            const float totalTime = 3.5f;
            var duration = new Duration(TimeSpan.FromSeconds(totalTime));
            var sb = new Storyboard
                         {
                             Duration = duration,
                         };

            Animate.AnimateScaleX(child, sb, duration, totalTime);
            Animate.AnimateScaleY(child, sb, duration, totalTime);
            //Animate.AnimatedBlur(child, sb, duration, totalTime);
            Animate.AnimateBrush(child, sb, duration, totalTime, brush);

            sb.Begin();
        }

        public void Refresh()
        {
            _timer.Stop();

            _positions = new bool[Cols,Rows];
            Children.Clear();
            BrushProvider.Initialize(BrushProviderInitialized);

            _timer.Start();
        }

        private void BrushProviderInitialized(IEnumerable<Brush> brushes)
        {
            var bricks = Combine(
                GetLayouts(),
                brushes,
                (layout, brush) => new {layout, brush});

            foreach (var brick in bricks)
            {
                Children.Add(CreateBrick(brick.layout, brick.brush));
            }
        }

        public static IEnumerable<TResult> Combine<T1, T2, TResult>(IEnumerable<T1> first, IEnumerable<T2> second,
                                                                    Func<T1, T2, TResult> func)
        {
            using (var e1 = first.GetEnumerator())
            using (var e2 = second.GetEnumerator())
            {
                while (e1.MoveNext() && e2.MoveNext())
                {
                    yield return func(e1.Current, e2.Current);
                }
            }
        }

        private IEnumerable<Layout> GetLayouts()
        {
            // large
            yield return new Layout {Size = 4, X = 0, Y = 0};

            //medium
            yield return new Layout {Size = 3, X = 9, Y = 0};
            yield return new Layout {Size = 3, X = 5, Y = 1};
            yield return new Layout {Size = 3, X = 8, Y = 3};
            yield return new Layout {Size = 3, X = 4, Y = 4};
            //yield return new Layout{Size=3,X=-1, Y=6};
            yield return new Layout {Size = 3, X = 2, Y = 7};
            yield return new Layout {Size = 3, X = 6, Y = 7};

            //small
            for (int x = 0; x < Cols; x++)
            {
                for (int y = 0; y < Rows; y++)
                {
                    if (!_positions[x, y])
                    {
                        yield return new Layout {Size = 1, X = x, Y = y};
                    }
                }
            }
        }

        private FrameworkElement CreateBrick(Layout layout, Brush brush)
        {
            var size = layout.Size;
            var x = layout.X;
            var y = layout.Y;

            MarkPosition(size, x, y);

            var fe = new Rectangle
                         {
                             Fill = brush,
                             Width = FrameSize*size,
                             Height = FrameSize*size,
                             RenderTransform = new CompositeTransform {ScaleX = 0, ScaleY = 0},
                             RenderTransformOrigin = new Point(0.5, 0.5),
                             Effect = new BlurEffect {Radius = 0}
                         };

            Canvas.SetLeft(fe, FrameSize*x);
            Canvas.SetTop(fe, FrameSize*y);

            AttachLoadingAnimation(fe, Random.Generator.NextDouble());

            return fe;
        }

        private static void AttachLoadingAnimation(Rectangle fe, double offset)
        {
            var offsetInMs = (int) (1000*offset);
            var beginTime = new TimeSpan(0, 0, 0, 0, offsetInMs);

            var duration = new Duration(TimeSpan.FromSeconds(1.5));
            var sb = new Storyboard
                         {
                             Duration = new Duration(TimeSpan.FromSeconds(5)),
                             BeginTime = beginTime
                         };

            var animX = CreateScaleAnimation(duration, beginTime);
            var animY = CreateScaleAnimation(duration, beginTime);

            sb.Children.Add(animX);
            sb.Children.Add(animY);

            Storyboard.SetTarget(animX, fe);
            Storyboard.SetTarget(animY, fe);

            Storyboard.SetTargetProperty(animX,
                                         new PropertyPath("(UIElement.RenderTransform).(CompositeTransform.ScaleX)"));
            Storyboard.SetTargetProperty(animY,
                                         new PropertyPath("(UIElement.RenderTransform).(CompositeTransform.ScaleY)"));

            sb.Begin();
        }

        private static DoubleAnimation CreateScaleAnimation(Duration duration, TimeSpan beginTime)
        {
            return new DoubleAnimation
                       {
                           BeginTime = beginTime,
                           Duration = duration,
                           From = 0,
                           To = 1,
                           EasingFunction = new QuarticEase {EasingMode = EasingMode.EaseOut}
                       };
        }

        private void MarkPosition(int size, int originX, int originY)
        {
            for (int x = originX; x < originX + size; x++)
            {
                for (int y = originY; y < originY + size; y++)
                {
                    _positions[x, y] = true;
                }
            }
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            return new Size(FrameSize*Cols, FrameSize*Rows);
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            foreach (FrameworkElement child in Children)
            {
                var x = Canvas.GetLeft(child) + MarginSize;
                var y = Canvas.GetTop(child) + MarginSize;
                var location = new Point(x, y);

                var w = child.Width - (2*MarginSize);
                var h = child.Height - (2*MarginSize);

                child.Arrange(new Rect(location, new Size(w, h)));
            }
            return new Size(FrameSize*Cols, FrameSize*Rows);
        }

        #region Nested type: Layout

        private struct Layout
        {
            public int Size;
            public int X;
            public int Y;
        }

        #endregion
    }
}